/*
 * LiquidBounce Hacked Client
 * A free open source mixin-based injection hacked client for Minecraft using Minecraft Forge.
 * https://github.com/CCBlueX/LiquidBounce/
 */
package net.ccbluex.liquidbounce.features.module.modules.exploit;

import net.ccbluex.liquidbounce.api.minecraft.client.network.IINetHandlerPlayClient;
import net.ccbluex.liquidbounce.api.minecraft.network.IPacket;
import net.ccbluex.liquidbounce.api.minecraft.network.play.client.ICPacketPlayer;
import net.ccbluex.liquidbounce.api.minecraft.util.IAxisAlignedBB;
import net.ccbluex.liquidbounce.api.minecraft.util.WBlockPos;
import net.ccbluex.liquidbounce.event.*;
import net.ccbluex.liquidbounce.features.module.Module;
import net.ccbluex.liquidbounce.features.module.ModuleCategory;
import net.ccbluex.liquidbounce.features.module.ModuleInfo;
import net.ccbluex.liquidbounce.utils.MovementUtils;
import net.ccbluex.liquidbounce.utils.block.BlockUtils;
import net.ccbluex.liquidbounce.utils.timer.TickTimer;
import net.ccbluex.liquidbounce.value.ListValue;

@ModuleInfo(name = "Phase", description = "Allows you to walk through blocks.", category = ModuleCategory.EXPLOIT)
public class Phase extends Module {

    private final ListValue modeValue = new ListValue("Mode", new String[] {"Vanilla", "Skip", "Spartan", "Clip", "AAC3.5.0", "Mineplex"}, "Vanilla");

    private final TickTimer tickTimer = new TickTimer();

    private boolean mineplexClip;
    private final TickTimer mineplexTickTimer = new TickTimer();

    @EventTarget
    public void onUpdate(final UpdateEvent event) {
        final boolean isInsideBlock = BlockUtils.collideBlockIntersects(mc.getThePlayer().getEntityBoundingBox(), block -> !classProvider.isBlockAir(block));

        if(isInsideBlock && !modeValue.get().equalsIgnoreCase("Mineplex")) {
            mc.getThePlayer().setNoClip(true);
            mc.getThePlayer().setMotionY(0D);
            mc.getThePlayer().setOnGround(false);
        }

        IINetHandlerPlayClient netHandlerPlayClient = mc.getNetHandler();

        switch(modeValue.get().toLowerCase()) {
            case "vanilla": {
                if (!mc.getThePlayer().getOnGround() || !tickTimer.hasTimePassed(2) || !mc.getThePlayer().isCollidedHorizontally() || !(!isInsideBlock || mc.getThePlayer().isSneaking()))
                    break;

                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(mc.getThePlayer().getPosX(), mc.getThePlayer().getPosY(), mc.getThePlayer().getPosZ(), true));
                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(0.5D, 0, 0.5D, true));
                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(mc.getThePlayer().getPosX(), mc.getThePlayer().getPosY(), mc.getThePlayer().getPosZ(), true));
                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(mc.getThePlayer().getPosX(), mc.getThePlayer().getPosY() + 0.2D, mc.getThePlayer().getPosZ(), true));
                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(0.5D, 0, 0.5D, true));
                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(mc.getThePlayer().getPosX() + 0.5D, mc.getThePlayer().getPosY(), mc.getThePlayer().getPosZ() + 0.5D, true));
                final double yaw = Math.toRadians(mc.getThePlayer().getRotationYaw());
                final double x = -Math.sin(yaw) * 0.04D;
                final double z = Math.cos(yaw) * 0.04D;
                mc.getThePlayer().setPosition(mc.getThePlayer().getPosX() + x, mc.getThePlayer().getPosY(), mc.getThePlayer().getPosZ() + z);
                tickTimer.reset();
                break;
            }
            case "skip": {
                if (!mc.getThePlayer().getOnGround() || !tickTimer.hasTimePassed(2) || !mc.getThePlayer().isCollidedHorizontally() || !(!isInsideBlock || mc.getThePlayer().isSneaking()))
                    break;

                final double direction = MovementUtils.getDirection();
                final double posX = -Math.sin(direction) * 0.3;
                final double posZ = Math.cos(direction) * 0.3;

                for (int i = 0; i < 3; ++i) {
                    mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(mc.getThePlayer().getPosX(), mc.getThePlayer().getPosY() + 0.06, mc.getThePlayer().getPosZ(), true));
                    mc.getNetHandler().addToSendQueue(classProvider.createCPacketPlayerPosition(mc.getThePlayer().getPosX() + posX * i, mc.getThePlayer().getPosY(), mc.getThePlayer().getPosZ() + posZ * i, true));
                }

                mc.getThePlayer().setEntityBoundingBox(mc.getThePlayer().getEntityBoundingBox().offset(posX, 0.0D, posZ));
                mc.getThePlayer().setPositionAndUpdate(mc.getThePlayer().getPosX() + posX, mc.getThePlayer().getPosY(), mc.getThePlayer().getPosZ() + posZ);
                tickTimer.reset();
                break;
            }
            case "spartan": {
                if (!mc.getThePlayer().getOnGround() || !tickTimer.hasTimePassed(2) || !mc.getThePlayer().isCollidedHorizontally() || !(!isInsideBlock || mc.getThePlayer().isSneaking()))
                    break;

                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(mc.getThePlayer().getPosX(), mc.getThePlayer().getPosY(), mc.getThePlayer().getPosZ(), true));
                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(0.5D, 0, 0.5D, true));
                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(mc.getThePlayer().getPosX(), mc.getThePlayer().getPosY(), mc.getThePlayer().getPosZ(), true));
                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(mc.getThePlayer().getPosX(), mc.getThePlayer().getPosY() - 0.2D, mc.getThePlayer().getPosZ(), true));
                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(0.5D, 0, 0.5D, true));
                netHandlerPlayClient.addToSendQueue(classProvider.createCPacketPlayerPosition(mc.getThePlayer().getPosX() + 0.5D, mc.getThePlayer().getPosY(), mc.getThePlayer().getPosZ() + 0.5D, true));
                final double yaw = Math.toRadians(mc.getThePlayer().getRotationYaw());
                final double x = -Math.sin(yaw) * 0.04D;
                final double z = Math.cos(yaw) * 0.04D;
                mc.getThePlayer().setPosition(mc.getThePlayer().getPosX() + x, mc.getThePlayer().getPosY(), mc.getThePlayer().getPosZ() + z);
                tickTimer.reset();
                break;
            }
            case "clip": {
                if (!tickTimer.hasTimePassed(2) || !mc.getThePlayer().isCollidedHorizontally() || !(!isInsideBlock || mc.getThePlayer().isSneaking()))
                    break;

                final double yaw = Math.toRadians(mc.getThePlayer().getRotationYaw());
                final double oldX = mc.getThePlayer().getPosX();
                final double oldZ = mc.getThePlayer().getPosZ();

                for (int i = 1; i <= 10; i++) {
                    final double x = -Math.sin(yaw) * i;
                    final double z = Math.cos(yaw) * i;

                    if (classProvider.isBlockAir(BlockUtils.getBlock(new WBlockPos(oldX + x, mc.getThePlayer().getPosY(), oldZ + z))) && classProvider.isBlockAir(BlockUtils.getBlock(new WBlockPos(oldX + x, mc.getThePlayer().getPosY() + 1, oldZ + z)))) {
                        mc.getThePlayer().setPosition(oldX + x, mc.getThePlayer().getPosY(), oldZ + z);
                        break;
                    }
                }
                tickTimer.reset();
                break;
            }
            case "aac3.5.0": {
                if (!tickTimer.hasTimePassed(2) || !mc.getThePlayer().isCollidedHorizontally() || !(!isInsideBlock || mc.getThePlayer().isSneaking()))
                    break;

                final double yaw = Math.toRadians(mc.getThePlayer().getRotationYaw());
                final double oldX = mc.getThePlayer().getPosX();
                final double oldZ = mc.getThePlayer().getPosZ();
                final double x = -Math.sin(yaw);
                final double z = Math.cos(yaw);

                mc.getThePlayer().setPosition(oldX + x, mc.getThePlayer().getPosY(), oldZ + z);
                tickTimer.reset();
                break;
            }
        }

        tickTimer.update();
    }

    @EventTarget
    public void onBlockBB(final BlockBBEvent event) {
        if (mc.getThePlayer() != null && BlockUtils.collideBlockIntersects(mc.getThePlayer().getEntityBoundingBox(), block -> !classProvider.isBlockAir(block)) && event.getBoundingBox() != null && event.getBoundingBox().getMaxY() > mc.getThePlayer().getEntityBoundingBox().getMinY() && !modeValue.get().equalsIgnoreCase("Mineplex")) {
            final IAxisAlignedBB axisAlignedBB = event.getBoundingBox();

            event.setBoundingBox(classProvider.createAxisAlignedBB(axisAlignedBB.getMaxX(), mc.getThePlayer().getEntityBoundingBox().getMinY(), axisAlignedBB.getMaxZ(), axisAlignedBB.getMinX(), axisAlignedBB.getMinY(), axisAlignedBB.getMinZ()));
        }
    }

    @EventTarget
    public void onPacket(final PacketEvent event) {
        final IPacket packet = event.getPacket();

        if (classProvider.isCPacketPlayer(packet)) {
            ICPacketPlayer packetPlayer = packet.asCPacketPlayer();

            if (modeValue.get().equalsIgnoreCase("AAC3.5.0")) {
                final float yaw = (float) MovementUtils.getDirection();

                packetPlayer.setX(packetPlayer.getX() - Math.sin(yaw) * 0.00000001D);
                packetPlayer.setZ(packetPlayer.getZ() + Math.cos(yaw) * 0.00000001D);
            }
        }
    }

    @EventTarget
    private void onMove(final MoveEvent event) {
        if (modeValue.get().equalsIgnoreCase("mineplex")) {
            if (mc.getThePlayer().isCollidedHorizontally())
                mineplexClip = true;
            if (!mineplexClip)
                return;

            mineplexTickTimer.update();

            event.setX(0);
            event.setZ(0);

            if (mineplexTickTimer.hasTimePassed(3)) {
                mineplexTickTimer.reset();
                mineplexClip = false;
            } else if (mineplexTickTimer.hasTimePassed(1)) {
                final double offset = mineplexTickTimer.hasTimePassed(2) ? 1.6D : 0.06D;
                final double direction = MovementUtils.getDirection();

                mc.getThePlayer().setPosition(mc.getThePlayer().getPosX() + (-Math.sin(direction) * offset), mc.getThePlayer().getPosY(), mc.getThePlayer().getPosZ() + (Math.cos(direction) * offset));
            }
        }
    }

    @EventTarget
    public void onPushOut(PushOutEvent event) {
        event.cancelEvent();
    }

    @Override
    public String getTag() {
        return modeValue.get();
    }
}
